package open5g.tools.edif

import scala.collection.mutable.ArrayBuffer
import scala.collection.SortedMap
import scala.util.parsing.combinator.syntactical._

// trait ParserElem

// case class elemIdent(name:String) extends ParserElem
// case class elemSkip extends ParserElem
case class port(val name:String,val dir:String,val width:Int)
case class instance(val name:String,val cell:String)
trait worv {
  val name : String
}
case class wire(val name:String) extends worv {
  override def toString = name
}
case class vector(val name:String, val n:Int) extends worv {
  override def toString = name + "_" + n.toString
}
case class signalPort(val inst:String,val portName:worv) {
  override def toString = inst+"_"+portName.toString
}
case class net(val name:String,signals:List[signalPort])

class portParser extends StandardTokenParsers {
  lexical.delimiters += ("(",")","&")
  lexical.reserved   += ("port",
    "array",
    "rename",
    "direction",
    "property",
    "string",
    "integer",
    "instance",
    "viewref",
    "cellref",
    "libraryref",
    "joined",
    "net",
    "portref",
    "instanceref",
    "member",
    "false",
    "true",
    "boolean",
    "owner")
  def signame : Parser[String] = {
    opt("&")~ident ^^ { case a~b => a match {
      case Some(a) => "C_C_" + b.toString 
      case None    => b.toString
    }}
  }
  def rename : Parser[String] = {
    ("("~"rename"~signame~stringLit~")") ^^ {case a~b~c~d~e => c.toString} 
  }
  def renameN : Parser[String] = {
    ("("~"rename"~signame~stringLit~")") ^^ {case a~b~c~d~e => c.toString+"<"+d+">"} 
  }
  def array : Parser[(String,Int)] = {
    ("("~"array"~rename~numericLit~")") ^^ {case a~b~c~d~e => (c,d.toInt)}
  }
  def direction : Parser[String] = {
    ("("~"direction"~ident~")") ^^ {case a~b~c~d => c.toString}
  }
  def pport : Parser[port] = {
    ("("~"port"~(ident|array|rename)~direction~rep(property)~")") ^^ {case a~b~c~d~e~f => c match {
      case (n:String,w:Int) => port(n,d,w)
      case a:String => port(a,d,1)
    }}
  }
  def pstring : Parser[String] = {
    ("("~"string"~stringLit~")") ^^ {case a~b~c~d => c.toString}
  }
  def pinteger : Parser[Int] = {
    ("("~"integer"~numericLit~")") ^^ {case a~b~c~d => c.toInt}
  }
  def pboolean : Parser[String] = {
    ("("~"boolean"~"("~("false"|"true")~")"~")") ^^ {case a~b~c~d~e~f => d.toString}
  }
  def powner: Parser[String] = {
    ("("~"owner"~stringLit~")") ^^ {case a~b~c~d => c.toString}
  }
  def property : Parser[Any] = {
    ("("~"property"~(ident|rename)~(pstring|pinteger|pboolean)~opt(powner)~")")
  }
  def pinstance : Parser[instance] = {
    ("("~"instance"~(ident|rename)~viewref~rep(property)~")") ^^ {case a~b~c~d~e~f => instance(c,d)}
  }
  def pinstanceN : Parser[instance] = {
    ("("~"instance"~(ident|renameN)~viewref~rep(property)~")") ^^ {case a~b~c~d~e~f => instance(c,d)}
  }
  def viewref : Parser[String] = {
    ("("~"viewref"~ident~cellref~")") ^^ {case a~b~c~d~e => d}
  }
  def cellref : Parser[String] = {
    ("("~"cellref"~ident~libraryref~")") ^^ {case a~b~c~d~e => c}
  }
  def libraryref : Parser[String] = {
    ("("~"libraryref"~ident~")") ^^ {case a~b~c~d => c}
  }
  def pnet : Parser[net] = {
    ("("~"net"~(ident|rename)~joined~")") ^^ {case a~b~c~d~e => net(c,d)}
  }
  def member : Parser[vector] = {
    ("("~"member"~signame~numericLit~")") ^^ { case a~b~c~d~e => vector(c,d.toInt)} 
  }
  def instanceref : Parser[String] = {
    ("("~"instanceref"~signame~")") ^^ {case a~b~c~d => c.toString}
  }
  def portref : Parser[signalPort] = {
    ("("~"portref"~(ident|member)~opt(instanceref)~")") ^^ {case a~b~c~d~f => {
        val wv = c match {
          case v:vector => v
          case i:String => wire(i)
        }
        val name = d match {
          case Some(a) => a
          case None => "self"
        }
        signalPort(name,wv)
      }
    }
  }
  def joined : Parser[List[signalPort]] = {
    ("("~"joined"~rep(portref)~")") ^^ {case a~b~c~d => c}
  }
  def parserAll[T]( p : Parser[T], input :String) = {
    phrase(p)( new lexical.Scanner(input))
  }
  def toPorts(edifFile:String,l:List[(Int,Int)]) = l.map{ case (s,e) =>
    val ssss = edifFile.substring(s,e+1)
    val r = parserAll(pport,ssss)
    // println(ssss,r)
    r match {
      case Success(p,_) => p
      case _ => port("None","INPUT",1)
    }
  }.map(x => (x.name -> x)).toMap
  def toInstances(edifFile:String,l:List[(Int,Int)]) = l.map{ case (s,e) =>
    val ssss = edifFile.substring(s,e+1)
    val r = parserAll(pinstance,ssss)
    r match {
      case Success(p,_) => p
      case _ => {
        println(ssss)
        instance("None","INPUT")
      }
    }
  }.map(x => (x.name -> x)).toMap
  def toInstancesN(edifFile:String,l:List[(Int,Int)]) = l.map{ case (s,e) =>
    val ssss = edifFile.substring(s,e+1)
    val r = parserAll(pinstanceN,ssss)
    r match {
      case Success(p,_) => p
      case _ => {
        println(ssss)
        instance("None","INPUT")
      }
    }
  }.map(x => (x.name -> x)).toMap
  def toNets(edifFile:String,l:List[(Int,Int)]) = l.map{ case (s,e) =>
    val ssss = edifFile.substring(s,e+1)
    val r = parserAll(pnet,ssss)
    r match {
      case Success(p,_) => p
      case _ => net("None",List[signalPort]())
    }
  }.map(x => (x.name -> x)).toMap

}

case class objMeta(val iType:String,val name:String,val level:Int)
case class obj(m:objMeta,children:List[obj]) {
  override def toString = {
    m.iType+"("+m.name+")"+":\n"+children.map(_.toString).reduce(_++"\n"++_)
  }
}
case class cell(  name      :String
                , ports     :Map[String,port]
                , instances :Map[String,instance]
                , nets      :Map[String,net]
                )

object handwork {
  def cut(f:String,s:Int,key:String) = {
    val ss = s+key.length+2
    var e = ss
    while(f.charAt(e) != '(' && e < f.length) e += 1
    f.substring(ss,e).trim()
  }
  def getLibs(edifFile:String) = {
    def findnet(ins:String,f:List[String]) : List[String] = {
      val i = ins.indexOf("net ")
      if(i == -1) f else {
        val dels = ins.drop(i-1)
        println(dels.take(5))
        
        val (l,e) = count(dels,0,0)
        
        println(s"find $i $l $e")
        
        if(e != -1) {
          findnet(dels.drop(e),dels.substring(0,e) :: f)
        } else f
      } 
    }
    findnet(edifFile,List[String]())
  }

  def count(line:String,start:Int,level:Int) = {
    def find(s:Int,l:Int) : (Int,Int) = {
      if(s == line.length()) (s,l) else {
        val c = line.charAt(s)
        if(c == '(') find(s+1,l+1) else if(c == ')') {
          if(l == 1) (s+1,0) else find(s+1,l-1) 
        }
        else find(s+1,l)
      }
    }
    val (end,l) = find(start,level)
    if(l != 0) (l,-1) else (l,end)
  }
  
  def scan(f:String) = {
    val r = new Array[ArrayBuffer[(Int,Int)]](20)
    val stack = new Array[Int](20)
    var level = 0
    var s = 0
    var max = 0
    for(i <- 0 until 20) r(i) = ArrayBuffer[(Int,Int)]()
    while(s < f.length()) {
      val c = f.charAt(s)
      if(c == '(') {
        stack(level) = s
        level += 1
      } else if(c == ')') {
        level -= 1
        r(level) += ((stack(level),s))
        if(level>max) max=level
      }
      s += 1
    }
    val rm = new Array[SortedMap[Int,Int]](max+1)
    for(i <- 0 to max) rm(i) = SortedMap[Int,Int]() ++ r(i).toMap
    rm
  }
}

case class handwork(val edifFile:String) {
  val eScan = handwork.scan(edifFile)
  val eLevel = eScan.length
  val parser = new portParser

  def key2level(l:Int,s:Int,e:Int,key:String) : (Int,List[(Int,Int)]) = {
    var level = l
    var find = false
    val skey = "("+key
    val len = skey.length
    val r = ArrayBuffer[(Int,Int)]()
    while(!find && level < eScan.length) {
      r.clear()
      for{(k,v) <- eScan(level);if k > s; if v < e; if edifFile.substring(k,k+len) == skey} {
        find = true
        r += ((k,v))
      }
      level += 1
    }
    (level,r.toList) 
  }
  
  def libwork : (Int,Int,Int) = {
    val (l,r) = key2level(0,0,edifFile.length,"Library")
    var find = false
    var start = -1
    var end = -1
    for{(k,v) <- r;if !find} {
      if(edifFile.substring(k,k+20).contains("work")) {
        find = true
        start = k
        end = v
      }
    }
    if (find) (l,start,end) else (0,0,0)
  }
  def getLib(ln:String) : (Int,Int,Int) = {
    val (l,r) = key2level(0,0,edifFile.length,"Library")
    var find = false
    var start = -1
    var end = -1
    for{(k,v) <- r;if !find} {
      if(edifFile.substring(k,k+20+ln.length).contains(ln)) {
        find = true
        start = k
        end = v
      }
    }
    if (find) (l,start,end) else (0,0,0)
  }
  def getCell(f:(Int,Int,Int)) : List[cell] = {
    val (l,s,e) = f
    
    if (l > 0) {
      val (lc,r) = key2level(l,s,e,"cell")
      r.map{ case (s,e) => parseCell(lc,s,e)} 
    }
    else List[cell]()
  }
  def getIns(f:(Int,Int,Int)) : Map[String,Map[String,instance]] = {
    val (l,s,e) = f
    
    if (l > 0) {
      val (lc,r) = key2level(l,s,e,"cell")
      r.map{ case (s,e) => parseInstanceN(lc,s,e)}.toMap 
    }
    else Map[String,Map[String,instance]]()
  }
  def workCell : List[cell] = {
    val (l,s,e) = libwork
    
    if (l > 0) {
      val (lc,r) = key2level(l,s,e,"cell")
      r.map{ case (s,e) => parseCell(lc,s,e)} 
    }
    else List[cell]()
  }
  def parseCell(l:Int,s:Int,e:Int) : cell = {
    val name = handwork.cut(edifFile,s,"cell")
    val (lp,ports) = key2level(l,s,e,"port")
    val (li,instances) = key2level(l,s,e,"instance")
    val (ln,nets) = key2level(l,s,e,"net")
    def toListString(l:List[(Int,Int)]) = l.map{ case (s,e) => edifFile.substring(s,e)}
    cell(name,parser.toPorts(edifFile,ports),parser.toInstances(edifFile,instances),parser.toNets(edifFile,nets))
  }

  def parseInstanceN(l:Int,s:Int,e:Int) = {
    val name = handwork.cut(edifFile,s,"cell")
    val (li,instances) = key2level(l,s,e,"instance")
    (name,parser.toInstancesN(edifFile,instances))
  }

  def cell2G(c:cell,g:Map[String,cell]) = {
    for((i,n) <- c.nets) yield {
      val signals = (for(s <- n.signals) yield {
              if (s.inst != "self"){
                val inst =  s.inst 
                // println(inst)
                val cellName = c.instances(inst).cell
                val portName = s.portName.name
                // println(inst,cellName,portName,g(cellName).ports(portName))
                if(g(cellName).ports(portName).dir == "OUTPUT") (1,inst) else (0,inst)
              } else {
                val portName = s.portName.name
                val inst = c.name + "_" + s.portName.toString
                if( c.ports(portName).dir == "OUTPUT") (0,inst) else (1,inst) 
              }
            })
      signals.groupBy(_._1)
    }
  }
  import java.io.PrintWriter
  import java.io.File
  def G2String(s:Map[Int,List[(Int,String)]],p:PrintWriter) = {
    if(s.contains(1)) {
      val o = s(1)(0)._2
      if(s.contains(0)) for(i <- s(0)) p.println(s"$o ${i._2}")
    }    
  }
  def I2String(c:Map[String,instance],p:PrintWriter) = {
    val m = for ((n,i) <- c) yield{
      (i.name,i.cell)
    }
    val mg = m.groupBy(_._2)
    for((n,is) <- mg) {
      p.println("<"+n+">")
      is.map{case (n,i) => n}.toList.sorted.foreach(p.println)
    }
  } 
}